﻿using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;
using Microsoft.Xna.Framework.Content;
 
namespace XNAProjectLibrary
{
    /// <summary>
    /// This is a game component that implements IUpdateable.
    /// </summary>
    public class Camera : Microsoft.Xna.Framework.GameComponent
    {
        #region Fields

        protected IInputHandler input;                                                          //inputhandler

        protected Matrix projection;                                                            //2 matrices: projection and view needed for 3D
        protected Matrix view;

        protected Vector3 cameraPosition = new Vector3(0.0f, 5.0f, 1.0f);                       //set up camera properties
        protected Vector3 cameraTarget = Vector3.Zero;
        protected Vector3 cameraUpVector = Vector3.Up;

        protected Vector3 cameraReference = new Vector3(0.0f, 0.0f, -1.0f);

        protected float cameraYaw;
        protected float cameraPitch;

        public float SpinRate;                                                                  //needed for update
        public float MoveRate;
        public float ScrollWheelRate;

        protected Vector3 movement = Vector3.Zero;                                              //movement is 0

        protected int playerIndex = 0;                                                          //??

        private Viewport? viewport;                                                             //wtf is the ? there?????

        public bool UpdateInput = true;                                                         //only updates of this is true

        public Matrix View                                                                      //getter method for view/projection matrix
        {
            get { return view; }
        }

        public Matrix Projection                                        
        {
            get { return projection; }
        }

        public PlayerIndex PlayerIndex                                                          //get/set for playerindex
        {
            get { return ((PlayerIndex)playerIndex); }
            set { playerIndex = (int)value; }
        }

        public Vector3 Position                                                                 //get set for camera properties
        {
            get { return (cameraPosition); }
            set { cameraPosition = value; }
        }

        public Vector3 Orientation
        {
            get { return (cameraReference); }
            set { cameraReference = value; }
        }

        public Vector3 Target
        {
            get { return (cameraTarget); }
            set { cameraTarget = value; }
        }

        public Viewport Viewport                                                                //get set for viewport
        {
            get
            {
                if (viewport == null)
                    viewport = Game.GraphicsDevice.Viewport;

                return ((Viewport)viewport);
            }
            set
            {
                viewport = value;
                InitializeCamera();
            }
        }

        #endregion

        public Camera(Game game)
            : base(game)
        {
            input = (IInputHandler)game.Services.GetService(typeof(IInputHandler));             //get inputhandler which is "GLOBAL"
        }

        public override void Initialize()                                                       //initialize base 
        {         
            base.Initialize();

            InitializeCamera();                                                                 //call custom init function
        }

        private void InitializeCamera()
        {
            float aspectRatio = (float)Game.GraphicsDevice.Viewport.Width /
                (float)Game.GraphicsDevice.Viewport.Height;
            Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, aspectRatio,
                1.0f, 10000.0f, out projection);                                                //set up projection matrix

            Matrix.CreateLookAt(ref cameraPosition, ref cameraTarget,
                ref cameraUpVector, out view);                                                  //set ip view matrix
        }

        public override void Update(GameTime gameTime)
        {
            if (!UpdateInput)                                                                   //do not update camere only base
            {
                base.Update(gameTime);
                return;
            }

            float timeDelta = (float)gameTime.ElapsedGameTime.TotalSeconds;                     //calculate elapsed time

            if (input.KeyboardState.IsKeyDown(Keys.Left) ||                                     //handle input . . . 
                (input.GamePads[playerIndex].ThumbSticks.Right.X < 0) ||
                (input.GamePads[playerIndex].DPad.Left == ButtonState.Pressed))
            {
                cameraYaw += (SpinRate * timeDelta);
            }
            if (input.KeyboardState.IsKeyDown(Keys.Right) ||
                (input.GamePads[playerIndex].ThumbSticks.Right.X > 0) ||
                (input.GamePads[playerIndex].DPad.Right == ButtonState.Pressed))
            {
                cameraYaw -= (SpinRate * timeDelta);
            }

            if (input.KeyboardState.IsKeyDown(Keys.Down) ||
                (input.GamePads[playerIndex].ThumbSticks.Right.Y < 0))
            {
                cameraPitch -= (SpinRate * timeDelta);
            }
            if (input.KeyboardState.IsKeyDown(Keys.Up) ||
                (input.GamePads[playerIndex].ThumbSticks.Right.Y > 0))
            {
                cameraPitch += (SpinRate * timeDelta);
            }

#if !XBOX360
            if ((input.PreviousMouseState.X > input.MouseState.X) &&
                (input.MouseState.LeftButton == ButtonState.Pressed))
            {
                cameraYaw += (SpinRate * timeDelta);
            }
            else if ((input.PreviousMouseState.X < input.MouseState.X) &&
                (input.MouseState.LeftButton == ButtonState.Pressed))
            {
                cameraYaw -= (SpinRate * timeDelta);
            }

            if ((input.PreviousMouseState.Y > input.MouseState.Y) &&
                (input.MouseState.LeftButton == ButtonState.Pressed))
            {
                cameraPitch += (SpinRate * timeDelta);
            }
            else if ((input.PreviousMouseState.Y < input.MouseState.Y) &&
                (input.MouseState.LeftButton == ButtonState.Pressed))
            {
                cameraPitch -= (SpinRate * timeDelta);
            }
#endif
            
            if (cameraYaw > 360)                                                                //reset camera angle if needed 
                cameraYaw -= 360;                                                               //right left direction
            else if (cameraYaw < 0)
                cameraYaw += 360;

                
            if (cameraPitch > 89)                                                               //up down direction
                cameraPitch = 89;                                                               //keep from flipping
            if (cameraPitch < -89)
                cameraPitch = -89;

            movement *= (MoveRate * timeDelta);                                                 //update movement

            Matrix rotationMatrix;                                                              //set up rotation matrix
            Matrix.CreateRotationY(MathHelper.ToRadians(cameraYaw), out rotationMatrix);        //rotation for right left

            if (movement != Vector3.Zero)                                                       //if there is movement
            {
                Vector3.Transform(ref movement, ref rotationMatrix, out movement);
                cameraPosition += movement;                                                     //update camera position
            }

            rotationMatrix = Matrix.CreateRotationX(MathHelper.ToRadians(cameraPitch)) *        //rotation for up down
                rotationMatrix;                                                                 //multiply matrices == apply both


            Vector3 transformedReference;                                                       //create vector showing camera facing
            Vector3.Transform(ref cameraReference, ref rotationMatrix,
                out transformedReference);

            Vector3.Add(ref cameraPosition, ref transformedReference, out cameraTarget);        //calculate the position the camera is looking at

            Matrix.CreateLookAt(ref cameraPosition, ref cameraTarget, ref cameraUpVector,
                out view);                                                                      //new lookat matrix to view


            base.Update(gameTime);                                                              //update base
        }

    }
}